package com.gitee.melin.bee.core.support;

import com.hubspot.jinjava.Jinjava;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * 在开发的时，设置虚拟机参数-DreloadSQLFiles=true. 可以重新加载sql语句配置文件， 避免修改配置文件，需要重新启动服务器。
 *
 * @author admin@gmail.com
 */
public class CustomSQL {
    private static final Logger LOG = LoggerFactory.getLogger(CustomSQL.class);

    private final Map<String, SQLBean> sqlPool = new ConcurrentHashMap<>();

    private final Map<String, String> jinjavaTemplates = new ConcurrentHashMap<>();

    private static final String STRING_SPACE = " ";

    private DocumentBuilder documentBuilder;

    private final Map<String, Long> configMap = new HashMap<>();

    private final Jinjava jinjava = new Jinjava();

    private boolean reloadSQLFiles = false;

    private static volatile CustomSQL instance = null;

    private CustomSQL() {}

    public static CustomSQL getInstance() {
        if (instance == null) {
            synchronized (CustomSQL.class) {
                if (instance == null) {
                    instance = new CustomSQL();
                    instance.init();
                }
            }
        }

        return instance;
    }

    public void init() {
        reloadSQLFiles = Boolean.parseBoolean(System.getProperty("reloadSQLFiles"));

        try {
            DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
            documentBuilder = documentBuilderFactory.newDocumentBuilder();

            Resource[] configs = loadConfigs();
            for (Resource config : configs) {
                LOG.info("Loading " + config.getURL().getPath());
                configMap.put(config.getURL().getPath(), config.lastModified());
                readXml(config.getInputStream());
            }
        } catch (Exception e) {
            LOG.error("", e);
        }
    }

    protected Resource[] loadConfigs() throws IOException {
        PathMatchingResourcePatternResolver patternResolver = new PathMatchingResourcePatternResolver();
        return patternResolver.getResources("classpath*:custom-sql/**/*.xml");
    }

    public String get(String id) {
        if (reloadSQLFiles) {
            try {
                reloadConfig();
            } catch (IOException e) {
                LOG.error(e.getMessage(), e);
            }
        }

        SQLBean bean = sqlPool.get(id);

        if (bean == null) {
            throw new IllegalStateException("sql id 不存在：" + id);
        }

        if ("simple".equals(bean.getTempateType())) {
            return sqlPool.get(id).getContent();
        } else {
            throw new RuntimeException("SQL 模板类型不正确，只可以是simple类型");
        }
    }

    public String get(String id, Map<String, Object> content) {
        if (reloadSQLFiles) {
            try {
                reloadConfig();
            } catch (IOException e) {
                LOG.error(e.getMessage(), e);
            }
        }

        if (!jinjavaTemplates.containsKey(id)) {
            throw new IllegalArgumentException("sql id 不存在：" + id);
        }
        String template = jinjavaTemplates.get(id);
        return jinjava.render(template, content);
    }

    /** 更新文件修改时间刷新。 */
    private void reloadConfig() throws IOException {
        Resource[] newConfigs = this.loadConfigs();
        for (Resource newConfig : newConfigs) {
            boolean flag = true;
            for (Entry<String, Long> entry : configMap.entrySet()) {
                if (newConfig.getURL().getPath().equals(entry.getKey())) {
                    flag = false;

                    if (newConfig.getFile().lastModified() != entry.getValue().longValue()) {
                        configMap.put(entry.getKey(), newConfig.getFile().lastModified());
                        readXml(newConfig.getInputStream());
                        LOG.info("Reloading " + entry.getKey());

                        break;
                    }
                }
            }

            if (flag) {
                configMap.put(newConfig.getURL().getPath(), newConfig.getFile().lastModified());
                readXml(newConfig.getInputStream());
                LOG.info("Reloading " + newConfig.getURL().getPath());
            }
        }
    }

    protected synchronized void readXml(InputStream inputStream) {
        if (inputStream == null) {
            return;
        }

        Document document;
        try {
            document = documentBuilder.parse(inputStream);
            document.getDocumentElement().normalize();
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage(), e);
        }

        NodeList list = document.getElementsByTagName("sql");
        for (int temp = 0; temp < list.getLength(); temp++) {
            Node node = list.item(temp);
            if (node.getNodeType() == Node.ELEMENT_NODE) {
                Element element = (Element) node;
                String id = element.getAttribute("id");
                String sqlType = element.getAttribute("sqlType");
                String tempateType = element.getAttribute("tempateType");

                if ("simple".equals(tempateType) || "jinja".equals(tempateType)) {
                    String content = transform(element.getTextContent());

                    SQLBean bean = new SQLBean();
                    bean.setTempateType(tempateType);
                    bean.setSqlType(sqlType);
                    bean.setContent(content);

                    if ("jinja".equals(tempateType)) {
                        jinjavaTemplates.put(id, content);
                    }

                    sqlPool.put(id, bean);
                } else {
                    LOG.warn("{} 对应 tempateType 值 {} 不正确，可选值为：simple 和 jinja", id, sqlType);
                }
            }
        }
    }

    protected String transform(String sql) {
        StringBuilder sb = new StringBuilder();

        try {
            BufferedReader bufferedReader = new BufferedReader(new StringReader(sql));

            String line = null;
            while ((line = bufferedReader.readLine()) != null) {
                sb.append(line.trim());
                sb.append(STRING_SPACE);
            }

            bufferedReader.close();
        } catch (IOException ioe) {
            return sql;
        }

        return sb.toString();
    }

    public static class SQLBean {
        /** 两种可选类型：simple和freeMarker */
        private String tempateType = "simple";

        /** 两种可选类型：SQL和HQL */
        private String sqlType = "SQL";

        private String content = "";

        public String getTempateType() {
            return tempateType;
        }

        public void setTempateType(String tempateType) {
            this.tempateType = tempateType;
        }

        public String getSqlType() {
            return sqlType;
        }

        public void setSqlType(String sqlType) {
            this.sqlType = sqlType;
        }

        public String getContent() {
            return content;
        }

        public void setContent(String content) {
            this.content = content;
        }
    }
}
